#include "../include/my_log.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/stat.h>
#include <stdio.h>
#include <time.h>

#include <string.h>
#include <stdarg.h>

#include <cerrno>
my_log* my_log::log=nullptr;

static u_char err_level[][20]={
    {"stderr"},    //0：控制台错误
    {"emerg"},     //1：紧急
    {"alert"},     //2：警戒
    {"crit"},      //3：严重
    {"error"},     //4：错误
    {"warn"},      //5：警告
    {"notice"},    //6：注意
    {"info"},      //7：信息
    {"debug"}      //8：调试
};
void my_log::write_log(int level, int err,const  char *fmt, ...)
{
    
    //这个函数改成组装好信息后，将信息放到

    char *now_log=new char[LOG_SIZE];
    time_t t;
    t=time(NULL);
  
    struct tm  tm;
    memset(&tm,0,sizeof(tm));
    localtime_r(&t,&tm);
    memset(now_log,0,LOG_SIZE); //清空
    snprintf(now_log,LOG_SIZE,"%4d/%02d/%02d %02d:%02d:%02d [%s] [pid=%d] [errno: %d]",tm.tm_year+1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec
                                                                    ,err_level[level],(int)getpid(),err);
    //得到fmt里的信息，拷贝进now_log里
    va_list args;
    va_start(args,fmt);
    int len=strlen(now_log);
    vsnprintf(now_log+len,LOG_SIZE-len,fmt,args);
    va_end(args);


    if(err){    //如果输入的是0 则表示没有错误信息
        //得到对应的错误信息
        this->get_err_info(now_log,err);
    }

    //写入进去就好了
    len=strlen(now_log);
    //int w_res;
    if(len>=2048){
        now_log[len-1]='\n';   //最后一个字符直接 用换行覆盖掉，不管了
       // w_res=write(this->fd,now_log,len);
    }else{
        now_log[len]='\n';   
        //w_res=write(this->fd,now_log,len+1);
    }

   //封装完毕
    pthread_mutex_lock(&this->mutex);

    this->wait_to_write_log.insert(std::make_pair(level,now_log));
    pthread_mutex_unlock(&this->mutex);
    // if(log->wait_to_write_log.size()>=5){   //这里设定只有map里的日志数量达到5才会唤醒并写入
    //     pthread_cond_signal(&this->cond_t);
    // }
    pthread_cond_signal(&this->cond_t);
    
  
    
}

my_log::~my_log()
{
    
    this->stop_flag=STOP_THREAD;    //标志结束
    pthread_cond_broadcast(&this->cond_t);
    pthread_join(this->handler,NULL);   //等待退出
    printf("日志线程退出完毕\n");
    //可能还有未写完的数据，这里写一下
    while(!this->wait_to_write_log.empty()){
        //拿取出来，然后执行write操作
        auto it=log->wait_to_write_log.begin();
        write(log->fd,it->second,strlen(it->second));
        delete[] it->second;
        log->wait_to_write_log.erase(it);

    }
    //不考虑失败的情况
    pthread_mutex_destroy(&this->mutex);
    if(log->fd!=stderr->_fileno){
        close(log->fd); //如果定位到的是标准出错，那么就没有关的必要
        log->fd=-1;
    }
    
}

my_log::my_log()
{

    this->fd=open(LOG_PATH,O_CREAT|O_WRONLY|O_APPEND,0664);  //不存在则创建文件  以只写打开文件 追加模式
    if(fd==-1){
        perror("open file error,fd=stderr");
        fd=stderr->_fileno;
    }
    //不考虑失败的情况
    
    pthread_mutex_init(&this->mutex,NULL);
    pthread_cond_init(&this->cond_t,NULL);
    this->is_running=false;

    this->stop_flag=RUNNING_THREAD; //标志启动
    
    if(pthread_create(&this->handler,NULL,my_log::doing_write_log_thread,this)!=0){
        perror("日志线程初始化失败");
        return ;
    }
    while(this->is_running==false){
        usleep(200*1000);   // 200ms
    }
    printf("日志线程开启，初始化成功\n");

}


void my_log::get_err_info(char *p, int err)
{
    if(strlen(p)>=LOG_SIZE){
        return; //长度写不下了，直接return吧，也不给出错信息了
    }

    char * buf=strerror(err);
    int len=strlen(p);
    strncat(p+len,buf,LOG_SIZE-len);
}


void *my_log::doing_write_log_thread(void *this_log)
{
    my_log * log=(my_log *)(this_log);    //强转
    
    while(log->stop_flag==RUNNING_THREAD){

        pthread_mutex_lock(&log->mutex);    //需要先上锁
        
        while(log->wait_to_write_log.empty()&&log->stop_flag==RUNNING_THREAD){      //如果没有日志需要写 ,并且程序还需要运行，那么就去睡觉
            log->is_running=true;
            pthread_cond_wait(&log->cond_t,&log->mutex);

        }
        if(log->stop_flag==STOP_THREAD&&log->wait_to_write_log.empty()==true){
            pthread_mutex_unlock(&log->mutex);
            break;  //如果是结束标志，直接退出
        }

        //真实写入
        
        while(log->wait_to_write_log.size()>0){
            //拿取出来，然后执行write操作
            auto it=log->wait_to_write_log.begin();
            if(write(log->fd,it->second,strlen(it->second))==-1){
                perror("写日志进行落盘时，写入失败！！");       // 这个日志也不要了，基本不会出错
            }
            
            delete[] it->second;
            log->wait_to_write_log.erase(it);
        }

        pthread_mutex_unlock(&log->mutex);


    }
    return nullptr;
}
